﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle.Peg;

using System;
using System.Collections.Generic;
using System.Linq;

using SCG = System.Collections.Generic;

using Nemerle2.Compiler;

namespace Nemerle2.Parser
{
  /// <summary>
  /// Description of MacroParser.
  /// </summary>
  [PegGrammar(
    start,
    grammar
    {
      any                     = ['\u0000'..'\uFFFF'];
      start : CompileUnit     = compileUnit !any;
      semicolon : NToken      = ";"s;
//      semicolonOpt            = (";"s)?;

      #region Line terminators

      newLineCharacter = '\n'
                        / '\r'
                        / '\u2028'    /*  line separator       */
                        / '\u2029';   /*  paragraph separator  */
      newLine   = "\r\n" / newLineCharacter;

      #endregion

      #region White space

      whitespace = [Zs]
                / '\t'
                / '\v'        /*  vertial tab          */
                / '\f';       /*  form feed            */

      #endregion

      #region Comments

      singleLineComment = "//" (!newLineCharacter any)*;
      delimitedComment = "/*" (!"*/" any)* "*/";
      comment = singleLineComment / delimitedComment;

      #endregion

      #region Spacer

      space = whitespace / newLine / comment;

      [InlineAllSubrules]
      s : void = space*;                      /* optional spacer          */
      S : void = !identifierPartCharacters s; /* identifier break spacer  */

      #endregion

      #region Identifier

      letterCharacter       = [Lu, Ll, Lt, Lm, Lo, Nl] / "'";
      combiningCharacter    = [Mn, Mc];
      decimalDigitCharacter = [Nd];
      connectingCharacter   = [Pc];
      formattingCharacter   = [Cf];

      identifierStartCharacter  = letterCharacter / "_";
      identifierPartCharacters  = letterCharacter / decimalDigitCharacter / connectingCharacter / combiningCharacter / formattingCharacter;
      identifierBody            = identifierStartCharacter identifierPartCharacters*;
      identifier            : PId = "@"? identifierBody s;

      namespaceName : LocatedList[PId] = identifier ("."s identifier)*;
      typeName      : LocatedList[PId] = identifier ("."s identifier)*;

      #endregion

      #region Namespaces

      usingAliasDirective     : PUsing = "using"S identifier "="s typeName semicolon;
      usingNamespaceDirective : PUsing = "using"S namespaceName semicolon;
      usingDirective          : PUsing = usingAliasDirective / usingNamespaceDirective;

      namespaceDeclarationStart   : LocatedList[PId] = "namespace"S namespaceName;
      namespaceDeclaration        : PAst = env { namespaceDeclarationStart "{"s usingDirective* namespaceMemberDeclaration* "}"s };
      namespaceTopDeclaration     : PAst = topDeclaration;
      namespaceMemberDeclaration  : PAst = namespaceDeclaration / namespaceTopDeclaration;

      #endregion

      #region Expression

      sequenceGroup  = "{"  anyCode "}";
      roundGroup     = "("  anyCode ")";
      squareGroup    = "["  anyCode "]";
      quoteGroup     = "<[" anyCode "]>";
      anyCode        = (space
                        / sequenceGroup
                        / roundGroup
                        / squareGroup
                        / quoteGroup
                        / !("}" / ")" / "]"> / "]>") any)*; //TODO: hc: add string literal

      #endregion


      #region Syntax macro

      syntaxKeyword = ("syntax" / "where" / "macro") !identifierPartCharacters;
      ruleIdentifier        : PId                = !syntaxKeyword identifier;
      braceRule             : PRule              = "("s orderedChoiceRule ")"s;
      refRule               : PRule              = ruleIdentifier;
      stringRule            : PRule              = '"' '"'; //TODO: hc: define string literal
      simpleRule            : PRule              = braceRule / refRule / stringRule;
      cardinalityRule       : PRule              = simpleRule ("?" / "+" / "*")?s;
      predicateRule         : PRule              = ("!" / "&")?s cardinalityRule;
      sequenceRule          : PRule              = predicateRule+;
      orderedChoiceRule     : PRule              = sequenceRule ("/"s sequenceRule)*;
      syntaxRule            : PRule              = orderedChoiceRule;
      namedSyntaxRule       : PRule              = identifier "="s syntaxRule;
      namedSyntaxRules      : LocatedList[PRule] = namedSyntaxRule (","s namedSyntaxRule)*;
      macroDeclaration      : PMember            = "macro"S identifier ":"s typeName "syntax"S syntaxRule ("where"S namedSyntaxRules)? sequenceGroup s;

      #endregion

      #region Top declarations

      macroClassDeclaration : PTypeDeclaration = "macro"S "class"S identifier "{"s macroDeclaration* "}"s;
      classDeclaration      : PTypeDeclaration = "class"S identifier;

      topDeclaration : PTypeDeclaration = macroClassDeclaration / classDeclaration;

      #endregion

      compileUnit : CompileUnit = global { s usingDirective* namespaceMemberDeclaration* };
  })]
  public partial class MacroParser
  {
    [RecordIgnore] envStack : SCG.Stack[GlobalEnv] = SCG.Stack(20);
    [RecordIgnore] mutable currentEnv : GlobalEnv;

    #region Scopes

    globalScopeBegin() : void
    {
      currentEnv = GlobalEnv(Project, Set(), "", Map());
      envStack.Clear();
      envStack.Push(currentEnv);
    }

    globalScopeEnd(_success : bool) : void
    {
    }

    envScopeBegin() : void
    {
      envStack.Push(currentEnv);
    }

    envScopeEnd(_success : bool) : void
    {
      currentEnv = envStack.Pop();
    }

    #endregion

    #region Identifier

    identifier(_at : NToken, idBody : NToken) : PId
    {
      PId(GetLocation(idBody), Project)
    }

    namespaceName(head : PId, tail :List[NToken * PId]) : LocatedList[PId]
    {
      def buffer = List(tail.Count + 1);
      buffer.Add(head);
      buffer.AddRange(tail.Select(t => t[1]));
      LocatedList(buffer.ToArray())
    }

    typeName(head : PId, tail :List[NToken * PId]) : LocatedList[PId]
    {
      def buffer = List(tail.Count + 1);
      buffer.Add(head);
      buffer.AddRange(tail.Select(t => t[1]));
      LocatedList(buffer.ToArray())
    }

    #endregion

    #region Namespaces

    usingAliasDirective(_using : NToken, name : PId, _eq : NToken, path : LocatedList[PId], _ : NToken) : PUsing
    {
      currentEnv = GlobalEnv(currentEnv, currentEnv.CurrentNamespace, [], [(name.Text, path)]);
      PUsing.Alias(Project, name, path)
    }

    usingNamespaceDirective(_using : NToken, path : LocatedList[PId], _ : NToken) : PUsing
    {
      currentEnv = GlobalEnv(currentEnv, currentEnv.CurrentNamespace, [$<#..$(path; ".")#>], []);
      PUsing.Open(Project, path)
    }

    namespaceDeclarationStart(_namespace : NToken, path : LocatedList[PId]) : LocatedList[PId]
    {
      currentEnv = GlobalEnv(currentEnv, $<#..$(path; ".")#>, [], []);
      path
    }

    namespaceDeclaration(path : LocatedList[PId], _ : NToken, usings : List[PUsing], namespaceMembers : List[PAst], _ : NToken) : PAst
    {
      def namespaces = List();
      def members = List();
      foreach(item in namespaceMembers)
      {
        | item is PNamespace       => namespaces.Add(item)
        | item is PTypeDeclaration => members.Add(item)
        | _ => assert2(false)
      }
      PNamespace.Content(
        project = Project,
        name = path,
        usings = LocatedList(Location(), usings.ToArray()),
        env = currentEnv,
        namespaces = LocatedList(Location(), namespaces.ToArray()),
        members = LocatedList(Location(), members.ToArray()));
    }

    namespaceTopDeclaration(declaration : PTypeDeclaration) : PAst
    {
      declaration
    }

    #endregion

    #region Syntax macro

    ruleIdentifier(id : PId) : PId
    {
      id
    }

    braceRule(_br1 : NToken, rule : PRule, _br2 : NToken) : PRule
    {
      rule
    }

    refRule(id : PId) : PRule
    {
      PRule.Ref(Project, id)
    }

    stringRule(_ : NToken, _ : NToken) : PRule
    {
      throw NotImplementedException()
    }

    cardinalityRule(rule : PRule, value : NToken) : PRule
    {
      if(value.IsEmpty)
        rule
      else
      {
        def c = match(GetText(value))
        {
          | "?" => PRuleCardinality.ZeroOrOne(GetLocation(value), Project)
          | "*" => PRuleCardinality.ZeroOrMany(GetLocation(value), Project)
          | "+" => PRuleCardinality.OneOrMany(GetLocation(value), Project)
          | _ => throw ArgumentOutOfRangeException("value")
        }
        PRule.Cardinality(Project, c, rule)
      }
    }

    predicateRule(value : NToken, rule : PRule) : PRule
    {
      if(value.IsEmpty)
        rule
      else
      {
        def p = match(GetText(value))
        {
          | "!" => PRulePredicate.Not(GetLocation(value), Project)
          | "&" => PRulePredicate.And(GetLocation(value), Project)
          | _ => throw ArgumentOutOfRangeException("value")
        }
        PRule.Predicate(Project, p, rule)
      }
    }

    sequenceRule(rules : List[PRule]) : PRule
    {
      if(rules.Count == 1)
        rules[0]
      else
        PRule.Sequence(Project, LocatedList(rules.ToArray()))
    }

    orderedChoiceRule(head : PRule, tail : List[NToken * PRule]) : PRule
    {
      if(tail.Count == 0)
        head
      else
      {
        def buffer = List();
        buffer.Add(head);
        foreach((_, rule) in tail)
          buffer.Add(rule);
        PRule.Choice(Project, LocatedList(buffer.ToArray()))
      }
    }

    syntaxRule(rule : PRule) : PRule
    {
      rule
    }

    namedSyntaxRule(id : PId, _eq : NToken, rule : PRule) : PRule
    {
      PRule.Named(Project, id, rule)
    }

    namedSyntaxRules(head : PRule, tail : List[NToken * PRule]) : LocatedList[PRule]
    {
      def buffer = List();
      buffer.Add(head);
      foreach((_, rule) in tail)
        buffer.Add(rule);
      LocatedList(buffer.ToArray())
    }

    macroDeclaration( _macro          : NToken,
                      id              : PId,
                      _colon          : NToken,
                      typeName        : LocatedList[PId],
                      _syntax         : NToken,
                      mainRule        : PRule,
                      whereClause     : option[NToken * LocatedList[PRule]],
                      code            : NToken) : PMember
    {
      def modifiers = PModifiers.Value(Project, Modifiers());
      def attrs = PCustomAttributes(Project, LocatedList.Empty());
      def name = Splicable.Name(id.Location, Project, id.Location.GetText(), currentEnv, 0);
      def additionalRules = match(whereClause)
      {
        | Some((_, additionalRules)) => additionalRules
        | _ => LocatedList.Empty()
      }
      PMember.Macro(Project, name, modifiers, attrs, typeName, mainRule, additionalRules, VToken(GetLocation(code), GetText(code)))
    }

    #endregion

    #region Top declaration

    macroClassDeclaration(_macro : NToken, _class : NToken, id : PId, _ : NToken, members: List[PMember], _ : NToken) : PTypeDeclaration
    {
      def modifiers = PModifiers.Value(Project, Modifiers());
      def attrs = PCustomAttributes(Project, LocatedList.Empty());
      def name = Splicable.Name(id.Location, Project, id.Location.GetText(), currentEnv, 0);
      PTypeDeclaration.MacroClass(Project, name, modifiers, attrs,
        Location(), LocatedList(members.ToArray()))
    }

    classDeclaration(_class : NToken, id : PId) : PTypeDeclaration
    {
      def modifiers = PModifiers.Value(Project, Modifiers());
      def attrs = PCustomAttributes(Project, LocatedList.Empty());
      def name = Splicable.Name(id.Location, Project, id.Location.GetText(), currentEnv, 0);
      def tyParms = PTypeParameters(Project);
      def constraints = PConstraints(Project);
      def parents = LocatedList.Empty();
      def members = LocatedList.Empty();
      PTypeDeclaration.Class(Project, name, modifiers, attrs,
        Location(), tyParms, constraints, parents, members)
    }

    #endregion

    start(unit : CompileUnit) : CompileUnit
    {
      unit
    }

    semicolon(semic : NToken) : NToken
    {
      semic
    }

    compileUnit(topUsings : List[PUsing], topMembers : List[PAst]) : CompileUnit
    {
      def namespaces = List();
      def members = List();
      foreach(item in topMembers)
      {
        | item is PNamespace       => namespaces.Add(item)
        | item is PTypeDeclaration => members.Add(item)
        | _ => assert2(false)
      }
      def globalNs = PNamespace.Content(
        project = Project,
        name = LocatedList.Empty(),
        usings = LocatedList(Location(), topUsings.ToArray()),
        env = currentEnv,
        namespaces = LocatedList(Location(), namespaces.ToArray()),
        members = LocatedList(Location(), members.ToArray()));
      def globalAttrs = PCustomAttributes(Project, LocatedList.Empty());
      CompileUnit.Content(Project, globalNs, globalAttrs)
    }
  }
}
